package net.aiion.weave.impl.bytecode;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import java.util.Stack;

import net.aiion.weave.asm.ClassWriter;
import net.aiion.weave.spi.bytecode.BytecodeContext;
import net.aiion.weave.spi.bytecode.BytecodeException;
import net.aiion.weave.spi.bytecode.BytecodeSink;
import net.aiion.weave.spi.bytecode.BytecodeVisitor;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;

public class BytecodeOutputStreamWriterSinkImpl implements BytecodeSink
{
    private final OutputStream outputStream;
    private final ClassWriter classWriter;

    private Stack<BytecodeVisitor> bytecodeVisitors;

    public BytecodeOutputStreamWriterSinkImpl(final OutputStream aOutputStream) {
        outputStream = aOutputStream;
        classWriter = new ClassWriter(0);
        bytecodeVisitors = new Stack<>();
        // mBytecodeVisitors.push(new BytecodeClassWriterVisitor(new CheckClassAdapter(mClassWriter)));
        bytecodeVisitors.push(new BytecodeClassWriterVisitor(classWriter));
    }

    @Override
    public void handleBytecodeEvent(final BytecodeContext aBytecodeContext) {
        final BytecodeEvent bytecodeEvent = aBytecodeContext.getBytecodeEvent();
        final BytecodeVisitor currentBytecodeVisitor = bytecodeVisitors.peek();
        final BytecodeVisitor newBytecodeVisitor = bytecodeEvent.acceptBytecodeVisitor(currentBytecodeVisitor);
        if (newBytecodeVisitor == null) {
            bytecodeVisitors.pop();
        } else if (!Objects.equals(currentBytecodeVisitor, newBytecodeVisitor)) {
            bytecodeVisitors.push(newBytecodeVisitor);
        }

        if (bytecodeEvent.getType() == BytecodeEvent.CLASS_END) {
            writeBytecode();
        }
    }

    private void writeBytecode() {
        try {
            outputStream.write(classWriter.toByteArray());
        } catch (final IOException e) {
            throw new BytecodeException(e);
        }
    }
}
